<!--
@license
Copyright 2017 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<link rel="import" href="../paper-slider/paper-slider.html">
<link rel="import" href="../paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="utils.html">

<!-- tf-op-table-styles contains shared styles used in this file -->
<dom-module id="tf-op-table-styles">
  <template>
    <style>
#time, #utilization {
  width: 60px;
}
#name {
  display: inline-block;
  min-width: 40%;
}
#row, #header {
  display: flex;
  align-items: center;
}
#row > *, #header > * {
  padding: 0.5em;
  overflow: hidden;
  flex-shrink: 0;
  box-sizing: border-box;
}
#header > * {
  padding-bottom: 0;
}
#provenance {
  flex: 1 1 0 ! important;
}
#utilization {
  text-align: right;
}
    </style>
  </template>
</dom-module>

<!--
  A tf-op-table shows a hierarchical profile using a tree of tf-op-table-entrys.
  One entry at a time can be made active (by clicking on it).
-->
<dom-module id="tf-op-table">
  <template>
    <style include="tf-op-table-styles">
:host {
  display: block;
  background-color: white;
}
#control {
  display: flex;
  flex-flow: row nowrap;
  justify-content: space-between;

  overflow:auto;
  text-transform:uppercase;
  padding: 0.5em;
  vertical-align: bottom;
  text-align: bottom;
}

.controlRowLeft {
  display: flex;
  flex-flow: row nowrap;
  align-items: center;
  line-height: 50px;
  text-align: bottom;
  justify-content: flex-start;
}

.controlRowRight{
  display: flex;
  flex-flow: row nowrap;
  align-items: center;
  line-height: 50px;
  text-align: bottom;
  justify-content: flex-end;
}

paper-slider {
  --paper-slider-input: {width: 100px}
  --paper-slider-height: 3px;
}

#header {
  font-size: smaller;
  font-weight: bold;
  color: #666;
  padding-bottom: 0.25em;
  border-bottom: 1px solid #666;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Match indented names */
#header > #name { padding-left: 2em; }
    </style>
    <div id="control">
    <!--
      paper-slider sets how many child nodes to show for each category.
      If paper-toggle-button is checked, then only children in top 90th
      percentile of time spent are listed.
    -->
    <span class="controlRowLeft">Show top
      <paper-slider min="10" max="100" snaps step="10"
        value="{{childrenCount}}" editable>
      </paper-slider>ops</span>
    <span class="controlRowRight">off&nbsp;
    <paper-toggle-button checked={{showP90}}>
    </paper-toggle-button>Top 90%</span>
    </div>
    <div id="header">
      <span id="time">Time</span>
      <span id="name">Name</span>
      <span id="provenance">TensorFlow Op</span>
      <span id="utilization">FLOPS</span>
    </div>
    <tf-op-table-entry node="[[rootNode]]"
        header-hover="[[_onHeaderHover]]"
        header-click="[[_onHeaderClick]]"
        children-count="{{childrenCount}}"
        show-p90="{{showP90}}"
        expanded="true">
    </tf-op-table-entry>
  </template>

  <script>
Polymer({
  is: 'tf-op-table',
  properties: {
    rootNode: {
      type: Object,
    },
    active: {
      type: Object,
      computed: '_active(_selected, _hover)',
      notify: true,
    },
    showP90: {
      type: Boolean,
      value: false,
      notify: true,
    },
    childrenCount: {
      type: Number,
      value: 10,
      notify: true,
    },
    _selected: {
      type: Object,
      value: null,
      notify: true,
    },
    _hover: {
      type: Object,
      value: null,
      notify: true,
    },
    /**
     * Handlers for hover/click events on entries.
     * We don't use two-way data binding on _hover and _selected, because
     * we get slowdown when they change.
     * @type {Function}
     */
    _onHeaderHover: {
      type: Object,
      value: function() { return (entry) => this._hover = entry; },
    },
    /** @type {Function} */
    _onHeaderClick: {
      type: Object,
      value: function() {
        return (entry) => {
          if (this._selected) this._selected.selected = false;
          this._selected = entry;
          entry.selected = true;
        };
      },
    },
  },
  _active: function(selected, hover) {
    if (hover) return hover.node;
    if (selected) return selected.node;
  },
});
  </script>
</dom-module>

<!--
  tf-op-table-entry is a row in a tf-op-table.
  It is an implementation detail of tf-op-table and should not be used directly.

  It describes an op, or group of ops (the node property).
  Its background contains a bar, whose size shows the time taken, and whose
  color shows the op's utilization.
  If the node has children, the tf-op-table-entry has child entries, and can be
  expanded/collapsed.
-->
<dom-module id="tf-op-table-entry">
  <template>
    <style include="tf-op-table-styles">
#row {
  position: relative;
  box-shadow: inset 0 1px 0 0 rgba(0,0,0,0.06);
  z-index: 0;
}
#bar {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: -1;
  background-color: #cde;
}
:host {
  display: block;
}
:host.selected {
  background-color: rgba(0,0,0,0.04);
}
:host.selected > #row {
  font-weight: bold;
}
#row:hover {
  background-color: rgba(0,0,0,0.05);
}
#disclosure {
  display: inline-block;
  width: 1em;
}
#name {
  font-family: monospace;
}
#time, #utilization {
  font-size: smaller;
}
/* Utilization has a background color, so it stretches to fill the row.
   Its text is in an inner div that remains vertically centered. */
#utilization { align-self: stretch; }
#utilization div { position: relative; top: 50%; transform: translateY(-50%); }
    </style>
    <div id="row"
         on-click="_handleHeaderClick"
         on-mouseenter="_handleHeaderMouseEnter"
         on-mouseleave="_handleHeaderMouseLeave"
         hidden="[[!level]]">
      <div id="bar" style$="width:{{_barWidth(node)}}"></div>
      <span id="time">{{_percent(node)}}</span>
      <span id="name" style$="padding-left:[[level]]em;">
        <span id="disclosure">
          <span hidden="[[!node.children.length]]">
            <span hidden="[[expanded]]">&#x25b6;</span>
            <span hidden="[[!expanded]]">&#x25bc;</span>
          </span>
        </span>{{node.name}}
      </span>
      <span id="provenance">{{_provenance(node)}}&nbsp;</span>
      <span id="utilization" hidden="[[!node.metrics]]"
        style$="background-color:{{_flameColor(node)}}">
        {{_utilization(node)}}</span>
    </div>
    <template is="dom-if" if="[[expanded]]">
      <template is="dom-repeat"
                items="{{_getKChildren(node, childrenCount, showP90, level)}}">
        <tf-op-table-entry
          node="[[item]]"
          children-count="{{childrenCount}}"
          show-p90="{{showP90}}"
          level={{_nextLevel(level)}}
          header-hover="{{headerHover}}"
          header-click="{{headerClick}}">
        </tf-op-table-entry>
      </template>
    </template>
  </template>

  <script>
Polymer({
  is: 'tf-op-table-entry',
  properties: {
    node: {
      type: Object,
    },
    level: {
      type: Number,
      value: 0,
    },
    /** @type {Function} */
    headerHover: {
      type: Object,
      value: () => function(entry) {},
      notify: true,
    },
    /** @type {Function} */
    headerClick: {
      type: Object,
      value: () => function(entry) {},
      notify: true,
    },
    expanded: {
      type: Boolean,
      value: false,
      notify: true,
    },
    selected: {
      type: Boolean,
      value: false,
      observer: '_selectedChanged',
    },
  },
  _eq: function(x, y) {
    return x == y;
  },
  _nextLevel: function(level) { return level + 1; },
  _handleHeaderClick: function(e) {
    this.expanded ^= true;
    this.headerClick(this);
  },
  _handleHeaderMouseEnter: function(e) { this.headerHover(this); },
  _handleHeaderMouseLeave: function(e) { this.headerHover(null); },
  _percent: function(node) {
    return (node.metrics && node.metrics.time)
              ? tf_op_profile.percent(node.metrics.time) : "";
  },
  _provenance: function(node) {
    return (node.xla && node.xla.provenance)
              ? node.xla.provenance.replace(/^.*\//, '') : "";
  },
  _utilization: function(node) {
    return tf_op_profile.percent(tf_op_profile.utilization(node));
  },
  _flameColor: function(node) {
    return tf_op_profile.flameColor(tf_op_profile.utilization(node), 1, 0.2);
  },
  _barWidth: function(node) {
    return (node.metrics && node.metrics.time)
              ? tf_op_profile.percent(node.metrics.time) : 0;
  },
  _selectedChanged: function(v) { this.classList.toggle('selected', v); },
  /*
    Returns top K children for the node. If p90 is true, then only children
    fallen in the top 90th percentile in time are returned. 
  */
  _getKChildren: function(node, k, p90, level) {
    if (p90 && node.children.length > 0 && node.children[0].metrics) {
      var tot = 0, i = 0, t90 = node.metrics.time * 0.9;
      for (; i < Math.min(k, node.children.length); i++) {
          if(tot >= t90) break;
          tot += node.children[i].metrics.time;
      }
      k = i;
    }
    return level ? node.children.slice(0, k) : node.children;
  },
});
  </script>
</dom-module>
